package com.weixing.mall.core.aspect;

import cn.hutool.crypto.SecureUtil;
import com.weixing.mall.base.constant.GlobalConstant;
import com.weixing.mall.base.enums.ResultCodeEnum;
import com.weixing.mall.base.wrapper.ResultUtil;
import com.weixing.mall.core.annotation.NoRepeatSubmit;
import com.weixing.mall.core.support.auth.AuthDto;
import com.weixing.mall.core.support.auth.AuthUtil;
import com.weixing.mall.core.utils.RequestUtil;
import com.weixing.mall.redis.RedissonLocker;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 表单防重复提交切面
 *
 * @Author: kfc
 * @Description: <br/>
 * Date:Create in 2019/8/9 9:38
 * @Modified By:
 */
@Slf4j
@Aspect
@Component
public class NoRepeatSubmitAspect {

    @Autowired
    private RedissonLocker redissonLocker;

    private String lockKey;
    private boolean isLocked = false;

    /**
     * 定义切入点：对要拦截的方法进行定义与限制，如包、类
     * <p>
     * 1、execution(public * *(..)) 任意的公共方法
     * 2、execution（* set*（..）） 以set开头的所有的方法
     * 3、execution（* com.lingyejun.annotation.LoggerApply.*（..））com.lingyejun.annotation.LoggerApply这个类里的所有的方法
     * 4、execution（* com.lingyejun.annotation.*.*（..））com.lingyejun.annotation包下的所有的类的所有的方法
     * 5、execution（* com.lingyejun.annotation..*.*（..））com.lingyejun.annotation包及子包下所有的类的所有的方法
     * 6、execution(* com.lingyejun.annotation..*.*(String,?,Long)) com.lingyejun.annotation包及子包下所有的类的有三个参数，第一个参数为String类型，第二个参数为任意类型，第三个参数为Long类型的方法
     * 7、execution(@annotation(com.lingyejun.annotation.Lingyejun))
     */
    @Pointcut("@annotation(com.weixing.mall.core.annotation.NoRepeatSubmit)")
    private void cutMethod() {
        System.out.println("方法拦截：加锁：" + System.currentTimeMillis());
    }

    /**
     * 前置通知：在目标方法执行前调用
     */
    @Before("cutMethod()")
    public void begin() {
//        this.lockKey = generatorLockKey();
    }


    /**
     * 环绕通知：灵活自由的在目标方法中切入代码
     */
    @Around("cutMethod()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        NoRepeatSubmit noRepeatSubmit = method.getAnnotation(NoRepeatSubmit.class);
        this.lockKey = generatorLockKey();
        if (log.isDebugEnabled()) {
            log.debug("lockkey:" + lockKey + " : " + System.currentTimeMillis());
        }
        isLocked = redissonLocker.fairLock(this.lockKey, TimeUnit.SECONDS, noRepeatSubmit.waitTime(), noRepeatSubmit.leaseTime());
        if (isLocked) {
            // 执行源方法
            return joinPoint.proceed();
        } else {
            if (log.isDebugEnabled()) {
                log.debug("未获取到锁");
            }
            return ResultUtil.error(ResultCodeEnum.CODE_10011006);
        }

    }

    /**
     * 后置/最终通知：无论目标方法在执行过程中出现一场都会在它之后调用
     */
    @After("cutMethod()")
    public void after() {
        if (log.isDebugEnabled()) {
            log.debug("方法执行完毕，释放锁:" + System.currentTimeMillis());
        }
        // 请求完成后释放锁
        if (isLocked) {
            redissonLocker.unlock(lockKey);
        }

    }


    /**
     * 生成key
     *
     * @return
     */
    private String generatorLockKey() {
        // 获取HttpServletRequest对象
        HttpServletRequest request = RequestUtil.getRequest();
        // 获取uti
        String requestURI = request.getRequestURI();
        // 获取请求类别
        String method = request.getMethod();
        // 获取登录用户
        AuthDto authDto = AuthUtil.getAuthDto();
        // 根据用户 + 请求地址 + 请求类别
        StringBuilder sb = new StringBuilder();
        sb.append(GlobalConstant.REDIS_PRE_NOREPEAT);
        sb.append(method)
                .append(requestURI)
                .append(authDto.getUserRole())
                .append(authDto.getUserId());
        return SecureUtil.md5().digestHex(sb.toString());
    }

}
